﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.Practices.Prism.Commands;

namespace MyFramework.Command.DelegateWatchCommand
{
    /// <summary>
    /// Delegate watch command
    /// </summary>
    public class DelegateWatchCommand : DelegateCommand
    {
        #region Fields

        /// <summary>
        /// The chains
        /// </summary>
        private readonly List<PropertyWatchChain> chains;

        /// <summary>
        /// The source
        /// </summary>
        private INotifyPropertyChanged source;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="DelegateWatchCommand"/> class.
        /// </summary>
        /// <param name="execute">The execute.</param>
        public DelegateWatchCommand(Action execute)
            : base(execute)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DelegateWatchCommand"/> class.
        /// </summary>
        /// <param name="execute">The execute.</param>
        /// <param name="canExecute">The can execute.</param>
        public DelegateWatchCommand(Action execute, Func<bool> canExecute)
            : base(execute, canExecute)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DelegateWatchCommand"/> class.
        /// </summary>
        /// <param name="execute">The execute.</param>
        /// <param name="canExecute">The can execute.</param>
        /// <param name="source">The source.</param>
        /// <param name="exprList">The expr list.</param>
        public DelegateWatchCommand(Action execute, Func<bool> canExecute, INotifyPropertyChanged source, params Expression<Func<INotifyPropertyChanged, object>>[] exprList)
            : base(execute, canExecute)
        {
            chains = exprList.Select(e => this.MakePropertyWatchChain(e)).ToList();
            this.Source = source;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the source.
        /// </summary>
        /// <value>
        /// The source.
        /// </value>
        public INotifyPropertyChanged Source
        {
            get { return source; }
            set
            {
                if (source != value)
                {
                    source = value;
                    foreach (var x in chains)
                    {
                        x.Head.Source = source;
                    }
                }
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Makes the property watch chain.
        /// </summary>
        /// <param name="expr">The expr.</param>
        /// <returns></returns>
        private PropertyWatchChain MakePropertyWatchChain(Expression<Func<INotifyPropertyChanged, object>> expr)
        {
            var ret = PropertyWatchChain.FromLambda(expr);
            ret.WatchedPropertyChanged += new EventHandler<EventArgs>(ret_WatchedPropertyChanged);
            return ret;
        }

        /// <summary>
        /// Handles the WatchedPropertyChanged event of the ret control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void ret_WatchedPropertyChanged(object sender, EventArgs e)
        {
            this.RaiseCanExecuteChanged();
        }

        #endregion
    }

    /// <summary>
    /// Delegate watch command
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class DelegateWatchCommand<T> : DelegateCommand where T : class, INotifyPropertyChanged
    {
        #region Fields

        /// <summary>
        /// The chains
        /// </summary>
        private readonly List<PropertyWatchChain> chains;

        /// <summary>
        /// The source
        /// </summary>
        private T source;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="DelegateWatchCommand&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="execute">The execute.</param>
        public DelegateWatchCommand(Action execute)
            : base(execute)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DelegateWatchCommand&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="execute">The execute.</param>
        /// <param name="canExecute">The can execute.</param>
        public DelegateWatchCommand(Action execute, Func<bool> canExecute)
            : base(execute, canExecute)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DelegateWatchCommand&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="execute">The execute.</param>
        /// <param name="canExecute">The can execute.</param>
        /// <param name="source">The source.</param>
        /// <param name="exprList">The expr list.</param>
        public DelegateWatchCommand(Action execute, Func<bool> canExecute, T source, params Expression<Func<T, object>>[] exprList)
            : base(execute, canExecute)
        {
            chains = exprList.Select(e => this.MakePropertyWatchChain(e)).ToList();
            this.Source = source;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the source.
        /// </summary>
        /// <value>
        /// The source.
        /// </value>
        public T Source
        {
            get { return source; }
            set
            {
                if (source != value)
                {
                    source = value;
                    foreach (var x in chains)
                    {
                        x.Head.Source = source;
                    }
                }
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Makes the property watch chain.
        /// </summary>
        /// <param name="expr">The expr.</param>
        /// <returns></returns>
        private PropertyWatchChain MakePropertyWatchChain(Expression<Func<T, object>> expr)
        {
            var ret = PropertyWatchChain.FromLambda(expr);
            ret.WatchedPropertyChanged += new EventHandler<EventArgs>(ret_WatchedPropertyChanged);
            return ret;
        }

        /// <summary>
        /// Handles the WatchedPropertyChanged event of the ret control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void ret_WatchedPropertyChanged(object sender, EventArgs e)
        {
            this.RaiseCanExecuteChanged();
        }

        #endregion
    }

    /// <summary>
    /// Delegate watch command
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="U"></typeparam>
    public class DelegateWatchCommand<T, U> : DelegateCommand<U> where T : class, INotifyPropertyChanged
    {
        #region Fields

        /// <summary>
        /// The chains
        /// </summary>
        private readonly List<PropertyWatchChain> chains;

        /// <summary>
        /// The source
        /// </summary>
        private T source;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="DelegateWatchCommand&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="execute">The execute.</param>
        public DelegateWatchCommand(Action<U> execute)
            : base(execute)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DelegateWatchCommand&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="execute">The execute.</param>
        /// <param name="canExecute">The can execute.</param>
        public DelegateWatchCommand(Action<U> execute, Func<U, bool> canExecute)
            : base(execute, canExecute)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DelegateWatchCommand&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="execute">The execute.</param>
        /// <param name="canExecute">The can execute.</param>
        /// <param name="source">The source.</param>
        /// <param name="exprList">The expr list.</param>
        public DelegateWatchCommand(Action<U> execute, Func<U, bool> canExecute, T source, params Expression<Func<T, object>>[] exprList)
            : base(execute, canExecute)
        {
            chains = exprList.Select(e => this.MakePropertyWatchChain(e)).ToList();
            this.Source = source;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the source.
        /// </summary>
        /// <value>
        /// The source.
        /// </value>
        public T Source
        {
            get { return source; }
            set
            {
                if (source != value)
                {
                    source = value;
                    foreach (var x in chains)
                    {
                        x.Head.Source = source;
                    }
                }
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Makes the property watch chain.
        /// </summary>
        /// <param name="expr">The expr.</param>
        /// <returns></returns>
        private PropertyWatchChain MakePropertyWatchChain(Expression<Func<T, object>> expr)
        {
            var ret = PropertyWatchChain.FromLambda(expr);
            ret.WatchedPropertyChanged += new EventHandler<EventArgs>(ret_WatchedPropertyChanged);
            return ret;
        }

        /// <summary>
        /// Handles the WatchedPropertyChanged event of the ret control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void ret_WatchedPropertyChanged(object sender, EventArgs e)
        {
            this.RaiseCanExecuteChanged();
        }

        #endregion
    }
}
